/*
	Copyright (C) 2006 yopyop
	Copyright (C) 2006 Theo Berkau
	Copyright (C) 2008-2015 DeSmuME team

	Ideas borrowed from Stephane Dallongeville's SCSP core

	This file is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This file is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with the this software.  If not, see <http://www.gnu.org/licenses/>.
*/


#define _USE_MATH_DEFINES
#include <math.h>
#ifndef M_PI
#define M_PI 3.1415926535897932386
#endif

#include <stdlib.h>
#include <string.h>
#include <libretro.h>
#include <queue>
#include <vector>

#include "debug.h"
#include "driver.h"
#include "MMU.h"
#include "SPU.h"
#include "mem.h"
#include "readwrite.h"
#include "armcpu.h"
#include "NDSSystem.h"
#include "matrix.h"
#include "utils/bits.h"

extern retro_audio_sample_batch_t audio_batch_cb;

static inline s16 read16(u32 addr) { return (s16)_MMU_read16<ARMCPU_ARM7,MMU_AT_DEBUG>(addr); }
static inline u8 read08(u32 addr) { return _MMU_read08<ARMCPU_ARM7,MMU_AT_DEBUG>(addr); }
static inline s8 read_s8(u32 addr) { return (s8)_MMU_read08<ARMCPU_ARM7,MMU_AT_DEBUG>(addr); }

#define K_ADPCM_LOOPING_RECOVERY_INDEX 99999
#define COSINE_INTERPOLATION_RESOLUTION 8192

//#ifdef FASTBUILD
	#undef FORCEINLINE
	#define FORCEINLINE
//#endif

SPU_struct *SPU_core = 0;
int SPU_currentCoreNum = SNDCORE_DUMMY;
static int volume = 100;


static size_t buffersize = 0;

//const int shift = (FORMAT == 0 ? 2 : 1);
static const int format_shift[] = { 2, 1, 3, 0 };
static const u8 volume_shift[] = { 0, 1, 2, 4 };

static const s8 indextbl[8] =
{
	-1, -1, -1, -1, 2, 4, 6, 8
};

static const u16 adpcmtbl[89] =
{
	0x0007, 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x0010,
	0x0011, 0x0013, 0x0015, 0x0017, 0x0019, 0x001C, 0x001F, 0x0022, 0x0025,
	0x0029, 0x002D, 0x0032, 0x0037, 0x003C, 0x0042, 0x0049, 0x0050, 0x0058,
	0x0061, 0x006B, 0x0076, 0x0082, 0x008F, 0x009D, 0x00AD, 0x00BE, 0x00D1,
	0x00E6, 0x00FD, 0x0117, 0x0133, 0x0151, 0x0173, 0x0198, 0x01C1, 0x01EE,
	0x0220, 0x0256, 0x0292, 0x02D4, 0x031C, 0x036C, 0x03C3, 0x0424, 0x048E,
	0x0502, 0x0583, 0x0610, 0x06AB, 0x0756, 0x0812, 0x08E0, 0x09C3, 0x0ABD,
	0x0BD0, 0x0CFF, 0x0E4C, 0x0FBA, 0x114C, 0x1307, 0x14EE, 0x1706, 0x1954,
	0x1BDC, 0x1EA5, 0x21B6, 0x2515, 0x28CA, 0x2CDF, 0x315B, 0x364B, 0x3BB9,
	0x41B2, 0x4844, 0x4F7E, 0x5771, 0x602F, 0x69CE, 0x7462, 0x7FFF
};

static const s16 wavedutytbl[8][8] = {
	{ -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF },
	{ -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF },
	{ -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
	{ -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
	{ -0x7FFF, -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
	{ -0x7FFF, -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
	{ -0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF, 0x7FFF },
	{ -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF, -0x7FFF }
};

static s32 precalcdifftbl[89][16];
static u8 precalcindextbl[89][8];

static const double ARM7_CLOCK = 33513982;

static const double samples_per_hline = (DESMUME_SAMPLE_RATE / 59.8261f) / 263.0f;

static double samples = 0;

static FORCEINLINE int MinMax(int val, int min, int max)
{
	if (val < min)
		return min;
	else if (val > max)
		return max;

	return val;
}

//--------------external spu interface---------------

int SPU_ChangeSoundCore(int coreid, int buffersize)
{
	int i;

	::buffersize = buffersize;

	return 0;
}

void SPU_ReInit(bool fakeBoot)
{
	SPU_Init(0, buffersize);

	// Firmware set BIAS to 0x200
	if (fakeBoot)
		SPU_WriteWord(0x04000504, 0x0200);
}

int SPU_Init(int coreid, int buffersize)
{
	int i, j;
	
	SPU_core = new SPU_struct((int)ceil(samples_per_hline));
	SPU_Reset();

	//create adpcm decode accelerator lookups
	for(i = 0; i < 16; i++)
	{
		for(j = 0; j < 89; j++)
		{
			precalcdifftbl[j][i] = (((i & 0x7) * 2 + 1) * adpcmtbl[j] / 8);
			if(i & 0x8) precalcdifftbl[j][i] = -precalcdifftbl[j][i];
		}
	}
	for(i = 0; i < 8; i++)
	{
		for(j = 0; j < 89; j++)
		{
			precalcindextbl[j][i] = MinMax((j + indextbl[i]), 0, 88);
		}
	}

	SPU_SetSynchMode(CommonSettings.SPU_sync_method);

	return SPU_ChangeSoundCore(coreid, buffersize);
}

void SPU_Pause(int pause)
{
}

void SPU_CloneUser()
{
}


void SPU_SetSynchMode(int method)
{
}

void SPU_ClearOutputBuffer()
{
}

void SPU_SetVolume(int volume)
{
}

void SPU_Reset(void)
{
	int i;

	SPU_core->reset();

	//zero - 09-apr-2010: this concerns me, regarding savestate synch.
	//After 0.9.6, lets experiment with removing it and just properly zapping the spu instead
	// Reset Registers
	for (i = 0x400; i < 0x51D; i++)
		T1WriteByte(MMU.ARM7_REG, i, 0);

	samples = 0;
}

//------------------------------------------

void SPU_struct::reset()
{
	memset(sndbuf,0,bufsize*2*4);
   memset(outbuf,0,768*2*sizeof(s16));

	memset((void *)channels, 0, sizeof(channel_struct) * 16);

	reconstruct(&regs);

	for(int i = 0; i < 16; i++)
	{
		channels[i].num = i;
	}
}

SPU_struct::SPU_struct(int buffersize)
	: bufpos(0)
	, buflength(0)
	, sndbuf(0)
   , outbuf(0)
   , outbufptr(0)
	, bufsize(buffersize)
{
	sndbuf = new s32[buffersize*2];
   outbuf = new s16[768*2];
	reset();
}

SPU_struct::~SPU_struct()
{
	if(sndbuf) delete[] sndbuf;
	if(outbuf) delete[] outbuf;
}

void SPU_DeInit(void)
{
	delete SPU_core; SPU_core=0;
}

//////////////////////////////////////////////////////////////////////////////

void SPU_struct::ShutUp()
{
	for(int i=0;i<16;i++)
		 channels[i].status = CHANSTAT_STOPPED;
}

static FORCEINLINE void adjust_channel_timer(channel_struct *chan)
{
	chan->sampinc = (((double)ARM7_CLOCK) / (DESMUME_SAMPLE_RATE * 2)) / (double)(0x10000 - chan->timer);
}

void SPU_struct::KeyProbe(int chan_num)
{
	channel_struct &thischan = channels[chan_num];
	if(thischan.status == CHANSTAT_STOPPED)
	{
		if(thischan.keyon && regs.masteren)
			KeyOn(chan_num);
	}
	else if(thischan.status == CHANSTAT_PLAY)
	{
		if(!thischan.keyon || !regs.masteren)
			KeyOff(chan_num);
	}
}

void SPU_struct::KeyOff(int channel)
{
	//printf("keyoff%d\n",channel);
	channel_struct &thischan = channels[channel];
	thischan.status = CHANSTAT_STOPPED;
}

void SPU_struct::KeyOn(int channel)
{
	channel_struct &thischan = channels[channel];
	thischan.status = CHANSTAT_PLAY;

	thischan.totlength = thischan.length + thischan.loopstart;
	adjust_channel_timer(&thischan);

	//printf("keyon %d totlength:%d\n",channel,thischan.totlength);


	//LOG("Channel %d key on: vol = %d, volumeDiv = %d, hold = %d, pan = %d, waveduty = %d, repeat = %d, format = %d, source address = %07X,"
	//		"timer = %04X, loop start = %04X, length = %06X, MMU.ARM7_REG[0x501] = %02X\n", channel, chan->vol, chan->volumeDiv, chan->hold, 
	//		chan->pan, chan->waveduty, chan->repeat, chan->format, chan->addr, chan->timer, chan->loopstart, chan->length, T1ReadByte(MMU.ARM7_REG, 0x501));

	switch(thischan.format)
	{
	case 0: // 8-bit
	//	thischan.loopstart = thischan.loopstart << 2;
	//	thischan.length = (thischan.length << 2) + thischan.loopstart;
		thischan.sampcnt = -3;
		break;
	case 1: // 16-bit
	//	thischan.loopstart = thischan.loopstart << 1;
	//	thischan.length = (thischan.length << 1) + thischan.loopstart;
		thischan.sampcnt = -3;
		break;
	case 2: // ADPCM
		{
			thischan.pcm16b = (s16)read16(thischan.addr);
			thischan.pcm16b_last = thischan.pcm16b;
			thischan.index = read08(thischan.addr + 2) & 0x7F;;
			thischan.lastsampcnt = 7;
			thischan.sampcnt = -3;
			thischan.loop_index = K_ADPCM_LOOPING_RECOVERY_INDEX;
		//	thischan.loopstart = thischan.loopstart << 3;
		//	thischan.length = (thischan.length << 3) + thischan.loopstart;
			break;
		}
	case 3: // PSG
		{
			thischan.sampcnt = -1;
			thischan.x = 0x7FFF;
			break;
		}
	default: break;
	}

	thischan.double_totlength_shifted = (double)(thischan.totlength << format_shift[thischan.format]);

	if(thischan.format != 3)
	{
		if(thischan.double_totlength_shifted == 0)
		{
			printf("INFO: Stopping channel %d due to zero length\n",channel);
			thischan.status = CHANSTAT_STOPPED;
		}
	}
}

//////////////////////////////////////////////////////////////////////////////

u8 SPU_struct::ReadByte(u32 addr)
{
   u8 * channel_timer;
   u8 * channel_loopstart;

   u8 * sound_bias = (u8 *)&regs.soundbias;

   u8 * cap0_dad = (u8 *)&regs.cap[0].dad;
   u8 * cap1_dad = (u8 *)&regs.cap[1].dad;
   u8 * cap0_len = (u8 *)&regs.cap[0].len;
   u8 * cap1_len = (u8 *)&regs.cap[1].len;

   //individual channel regs
   if ((addr & 0x0F00) == 0x0400)
   {
      u32 chan_num = (addr >> 4) & 0xF;
      channel_struct &thischan = channels[chan_num];

      channel_timer     = (u8 *)&thischan.timer;
      channel_loopstart = (u8 *)&thischan.loopstart;
      switch (addr & 0xF)
      {
         case 0x0:
            return thischan.vol;
         case 0x1:
            return (thischan.volumeDiv | (thischan.hold << 7));
         case 0x2:
            return thischan.pan;
         case 0x3:
            return (	thischan.waveduty
                  | (thischan.repeat << 3)
                  | (thischan.format << 5)
                  | ((thischan.status == CHANSTAT_PLAY)?0x80:0)
                  );
         case 0x8:
            return channel_timer[0];
         case 0x9:
            return channel_timer[1];
         case 0xA:
            return channel_loopstart[0];
         case 0xB:
            return channel_loopstart[1];
      }
      return 0;
   }

   switch(addr)
   {
      //SOUNDCNT
      case 0x500:
         return regs.mastervol;
      case 0x501:
         return (regs.ctl_left
               | (regs.ctl_right << 2)
               | (regs.ctl_ch1bypass << 4)
               | (regs.ctl_ch3bypass << 5)
               | (regs.masteren << 7)
               );

         //SOUNDBIAS
      case 0x504:
         return sound_bias[0];
      case 0x505:
         return sound_bias[1];

         //SNDCAP0CNT/SNDCAP1CNT
      case 0x508:
      case 0x509:
         {
            u32 which = (addr - 0x508);
            return regs.cap[which].add
               | (regs.cap[which].source << 1)
               | (regs.cap[which].oneshot << 2)
               | (regs.cap[which].bits8 << 3)
               | (regs.cap[which].runtime.running << 7);
         }	

         //SNDCAP0DAD
      case 0x510:
         return cap0_dad[0];
      case 0x511:
         return cap0_dad[1];
      case 0x512:
         return cap0_dad[2];
      case 0x513:
         return cap0_dad[3];

         //SNDCAP0LEN
      case 0x514:
         return cap0_len[0];
      case 0x515:
         return cap0_len[1];

         //SNDCAP1DAD
      case 0x518:
         return cap1_dad[0];
      case 0x519:
         return cap1_dad[1];
      case 0x51A:
         return cap1_dad[2];
      case 0x51B:
         return cap1_dad[3];

         //SNDCAP1LEN
      case 0x51C:
         return cap1_len[0];
      case 0x51D:
         return cap1_len[1];
   } //switch on address

   return 0;
}

u16 SPU_struct::ReadWord(u32 addr)
{
	u16 * cap0_dad = (u16 *)&regs.cap[0].dad;
	u16 * cap1_dad = (u16 *)&regs.cap[1].dad;

	//individual channel regs
	if ((addr & 0x0F00) == 0x0400)
	{
		u32 chan_num = (addr >> 4) & 0xF;
		channel_struct &thischan=channels[chan_num];

		switch (addr & 0xF)
		{
			case 0x0: return	(thischan.vol
								| (thischan.volumeDiv << 8)
								| (thischan.hold << 15)
								);
			case 0x2: return	(thischan.pan
								| (thischan.waveduty << 8)
								| (thischan.repeat << 11)
								| (thischan.format << 13)
								| ((thischan.status == CHANSTAT_PLAY)?(1 << 15):0)
								);
			case 0x8: return thischan.timer;
			case 0xA: return thischan.loopstart;
		} //switch on individual channel regs
		return 0;
	}

	switch(addr)
	{
		//SOUNDCNT
		case 0x500: return	(regs.mastervol
					| (regs.ctl_left << 8)
					| (regs.ctl_right << 10)
					| (regs.ctl_ch1bypass << 12)
					| (regs.ctl_ch3bypass << 13)
					| (regs.masteren << 15)
					);

		//SOUNDBIAS
		case 0x504: return regs.soundbias;
	
		//SNDCAP0CNT/SNDCAP1CNT
		case 0x508:
		{
			u8 val0 =	regs.cap[0].add
						| (regs.cap[0].source << 1)
						| (regs.cap[0].oneshot << 2)
						| (regs.cap[0].bits8 << 3)
						| (regs.cap[0].runtime.running << 7);
			u8 val1 =	regs.cap[1].add
						| (regs.cap[1].source << 1)
						| (regs.cap[1].oneshot << 2)
						| (regs.cap[1].bits8 << 3)
						| (regs.cap[1].runtime.running << 7);
			return (u16)(val0 | (val1 << 8));
		}

		//SNDCAP0DAD
		case 0x510: return cap0_dad[0];
		case 0x512: return cap0_dad[1];

		//SNDCAP0LEN
		case 0x514: return regs.cap[0].len;

		//SNDCAP1DAD
		case 0x518: return cap1_dad[0];
		case 0x51A: return cap1_dad[1];

		//SNDCAP1LEN
		case 0x51C: return regs.cap[1].len;
	} //switch on address

	return 0;
}

u32 SPU_struct::ReadLong(u32 addr)
{
	//individual channel regs
	if ((addr & 0x0F00) == 0x0400)
	{
		u32 chan_num = (addr >> 4) & 0xF;
		channel_struct &thischan=channels[chan_num];

		switch (addr & 0xF)
		{
			case 0x0: return	(thischan.vol
								| (thischan.volumeDiv << 8)
								| (thischan.hold << 15)
								| (thischan.pan << 16)
								| (thischan.waveduty << 24)
								| (thischan.repeat << 27)
								| (thischan.format << 29)
								| ((thischan.status == CHANSTAT_PLAY)?(1 << 31):0)
								);
			case 0x8: return (thischan.timer | (thischan.loopstart << 16));
		} //switch on individual channel regs
		return 0;
	}

	switch(addr)
	{
		//SOUNDCNT
		case 0x500: return	(regs.mastervol
							| (regs.ctl_left << 8)
							| (regs.ctl_right << 10)
							| (regs.ctl_ch1bypass << 12)
							| (regs.ctl_ch3bypass << 13)
							| (regs.masteren << 15)
							);

		//SOUNDBIAS
		case 0x504: return (u32)regs.soundbias;
	
		//SNDCAP0CNT/SNDCAP1CNT
		case 0x508:
		{
			u8 val0 =	regs.cap[0].add
						| (regs.cap[0].source << 1)
						| (regs.cap[0].oneshot << 2)
						| (regs.cap[0].bits8 << 3)
						| (regs.cap[0].runtime.running << 7);
			u8 val1 =	regs.cap[1].add
						| (regs.cap[1].source << 1)
						| (regs.cap[1].oneshot << 2)
						| (regs.cap[1].bits8 << 3)
						| (regs.cap[1].runtime.running << 7);
			return (u32)(val0 | (val1 << 8));
		}	

		//SNDCAP0DAD
		case 0x510: return regs.cap[0].dad;

		//SNDCAP0LEN
		case 0x514: return (u32)regs.cap[0].len;

		//SNDCAP1DAD
		case 0x518: return regs.cap[1].dad;

		//SNDCAP1LEN
		case 0x51C: return (u32)regs.cap[1].len;
	} //switch on address
	
	return 0;
}

SPUFifo::SPUFifo()
{
	reset();
}

void SPUFifo::reset()
{
	head = tail = size = 0;
}

void SPUFifo::enqueue(s16 val)
{
	if(size==16) return;
	buffer[tail] = val;
	tail++;
	tail &= 15;
	size++;
}

s16 SPUFifo::dequeue()
{
	if(size==0) return 0;
	head++;
	head &= 15;
	s16 ret = buffer[head];
	size--;
	return ret;
}

void SPUFifo::save(EMUFILE* fp)
{
	u32 version = 1;
	write32le(version,fp);
	write32le(head,fp);
	write32le(tail,fp);
	write32le(size,fp);
	for(int i=0;i<16;i++)
		write16le(buffer[i],fp);
}

bool SPUFifo::load(EMUFILE* fp)
{
	u32 version;
	if(read32le(&version,fp) != 1) return false;
	read32le(&head,fp);
	read32le(&tail,fp);
	read32le(&size,fp);
	for(int i=0;i<16;i++)
		read16le(&buffer[i],fp);
	return true;
}

void SPU_struct::ProbeCapture(int which)
{
	//VERY UNTESTED -- HOW MUCH OF THIS RESETS, AND WHEN?

	if(!regs.cap[which].active)
	{
		regs.cap[which].runtime.running = 0;
		return;
	}

	REGS::CAP &cap = regs.cap[which];
	cap.runtime.running = 1;
	cap.runtime.curdad = cap.dad;
	u32 len = cap.len;
	if(len==0) len=1;
	cap.runtime.maxdad = cap.dad + len*4;
	cap.runtime.sampcnt = 0;
	cap.runtime.fifo.reset();
}

void SPU_struct::WriteByte(u32 addr, u8 val)
{
	u8 * sound_bias = (u8 *)&regs.soundbias;
	u8 * cap0_dad   = (u8 *)&regs.cap[0].dad;
	u8 * cap1_dad   = (u8 *)&regs.cap[1].dad;
	u8 * cap0_len   = (u8 *)&regs.cap[0].len;
	u8 * cap1_len   = (u8 *)&regs.cap[1].len;

	//individual channel regs
	if ((addr & 0x0F00) == 0x0400)
	{
		u8 chan_num = (addr >> 4) & 0xF;
		channel_struct &thischan = channels[chan_num];

		u8 *channel_address = (u8 *)&thischan.addr;
		u8 *channel_timer = (u8 *)&thischan.timer;
		u8 *channel_loopstart = (u8 *)&thischan.loopstart;
		u8 *channel_length = (u8 *)&thischan.length;

		//printf("SPU write08 channel%d, reg %04X, val %02X\n", chan_num, addr, val);

		switch (addr & 0x000F)
		{
			case 0x0: thischan.vol = (val & 0x7F); break;
			case 0x1:
				thischan.volumeDiv = (val & 0x03);
				thischan.hold = (val >> 7) & 0x01;
				break;
			case 0x2: thischan.pan = (val & 0x7F); break;
			case 0x3:
				thischan.waveduty = (val & 0x07);
				thischan.repeat = (val >> 3) & 0x03;
				thischan.format = (val >> 5) & 0x03;
				thischan.keyon = (val >> 7) & 0x01;
				KeyProbe(chan_num);
				break;
			case 0x4: channel_address[0] = (val & 0xFC); break;
			case 0x5: channel_address[1] = val; break;
			case 0x6: channel_address[2] = val; break;
			case 0x7: channel_address[3] = (val & 0x07); break; //only 27 bits of this register are used
			case 0x8: channel_timer[0] = val; adjust_channel_timer(&thischan); break;
			case 0x9: channel_timer[1] = val; adjust_channel_timer(&thischan); break;

			case 0xA: channel_loopstart[0] = val; break;
			case 0xB: channel_loopstart[1] = val; break;
			case 0xC: channel_length[0] = val; break;
			case 0xD: channel_length[1] = val; break;
			case 0xE: channel_length[2] = (val & 0x3F); break; //only 22 bits of this register are used
			case 0xF: channel_length[3] = 0; break;
		} //switch on individual channel regs
		return;
	}

	switch(addr)
	{
		//SOUNDCNT
		case 0x500: regs.mastervol = (val & 0x7F); break;
		case 0x501:
			regs.ctl_left  = (val >> 0) & 3;
			regs.ctl_right = (val >> 2) & 3;
			regs.ctl_ch1bypass = (val >> 4) & 1;
			regs.ctl_ch3bypass = (val >> 5) & 1;
			regs.masteren = (val >> 7) & 1;
			for(u8 i=0; i<16; i++)
				KeyProbe(i);
		break;
		
		//SOUNDBIAS
		case 0x504: sound_bias[0] = val; break;
		case 0x505: sound_bias[1] = (val & 0x03); break;

		//SNDCAP0CNT/SNDCAP1CNT
		case 0x508:
		case 0x509:
		{
			u32 which = (addr - 0x508);
			regs.cap[which].add = BIT0(val);
			regs.cap[which].source = BIT1(val);
			regs.cap[which].oneshot = BIT2(val);
			regs.cap[which].bits8 = BIT3(val);
			regs.cap[which].active = BIT7(val);
			ProbeCapture(which);
			break;
		}

		//SNDCAP0DAD
		case 0x510: cap0_dad[0] = (val & 0xFC); break;
		case 0x511: cap0_dad[1] = val; break;
		case 0x512: cap0_dad[2] = val; break;
		case 0x513: cap0_dad[3] = (val & 0x07); break;

		//SNDCAP0LEN
		case 0x514: cap0_len[0] = val; break;
		case 0x515: cap0_len[1] = val; break;

		//SNDCAP1DAD
		case 0x518: cap1_dad[0] = (val & 0xFC); break;
		case 0x519: cap1_dad[1] = val; break;
		case 0x51A: cap1_dad[2] = val; break;
		case 0x51B: cap1_dad[3] = (val & 0x07); break;

		//SNDCAP1LEN
		case 0x51C: cap1_len[0] = val; break;
		case 0x51D: cap1_len[1] = val; break;
	} //switch on address
}

void SPU_struct::WriteWord(u32 addr, u16 val)
{
	u16 * cap0_dad = (u16 *)&regs.cap[0].dad;
	u16 * cap1_dad = (u16 *)&regs.cap[1].dad;

	//individual channel regs
	if ((addr & 0x0F00) == 0x0400)
	{
		u32 chan_num = (addr >> 4) & 0xF;
		channel_struct &thischan = channels[chan_num];
		u16 *channel_address = (u16 *)&thischan.addr;
		u16 *channel_length = (u16 *)&thischan.length;

		//printf("SPU write16 channel%d, reg %04X, val %04X\n", chan_num, addr, val);

		switch (addr & 0xF)
		{
			case 0x0:
				thischan.vol = (val & 0x7F);
				thischan.volumeDiv = (val >> 8) & 0x3;
				thischan.hold = (val >> 15) & 0x1;
				break;
			case 0x2:
				thischan.pan = (val & 0x7F);
				thischan.waveduty = (val >> 8) & 0x7;
				thischan.repeat = (val >> 11) & 0x3;
				thischan.format = (val >> 13) & 0x3;
				thischan.keyon = (val >> 15) & 0x1;
				KeyProbe(chan_num);
				break;
			case 0x4: channel_address[0] = (val & 0xFFFC); break;
			case 0x6: channel_address[1] = (val & 0x07FF); break;
			case 0x8: thischan.timer = val; adjust_channel_timer(&thischan); break;
			case 0xA: thischan.loopstart = val; break;
			case 0xC: channel_length[0] = val; break;
			case 0xE: channel_length[1] = (val & 0x003F); break; //only 22 bits of this register are used
		} //switch on individual channel regs
		return;
	}

	switch (addr)
	{
		//SOUNDCNT
		case 0x500:
			regs.mastervol = (val & 0x7F);
			regs.ctl_left  = (val >> 8) & 0x03;
			regs.ctl_right = (val >> 10) & 0x03;
			regs.ctl_ch1bypass = (val >> 12) & 0x01;
			regs.ctl_ch3bypass = (val >> 13) & 0x01;
			regs.masteren = (val >> 15) & 0x01;
			for(u8 i=0; i<16; i++)
				KeyProbe(i);
			break;

		//SOUNDBIAS
		case 0x504: regs.soundbias = (val & 0x3FF); break;

		//SNDCAP0CNT/SNDCAP1CNT
		case 0x508:
		{
			regs.cap[0].add = BIT0(val);
			regs.cap[0].source = BIT1(val);
			regs.cap[0].oneshot = BIT2(val);
			regs.cap[0].bits8 = BIT3(val);
			regs.cap[0].active = BIT7(val);
			ProbeCapture(0);

			regs.cap[1].add = BIT8(val);
			regs.cap[1].source = BIT9(val);
			regs.cap[1].oneshot = BIT10(val);
			regs.cap[1].bits8 = BIT11(val);
			regs.cap[1].active = BIT15(val);
			ProbeCapture(1);
			break;
		}

		//SNDCAP0DAD
		case 0x510: cap0_dad[0] = (val & 0xFFFC); break;
		case 0x512: cap0_dad[1] = (val & 0x7FFF); break;

		//SNDCAP0LEN
		case 0x514: regs.cap[0].len = val; break;

		//SNDCAP1DAD
		case 0x518: cap1_dad[0] = (val & 0xFFFC); break;
		case 0x51A: cap1_dad[1] = (val & 0x7FFF); break;

		//SNDCAP1LEN
		case 0x51C: regs.cap[1].len = val; break;
	} //switch on address
}

void SPU_struct::WriteLong(u32 addr, u32 val)
{
	//individual channel regs
	if ((addr & 0x0F00) == 0x0400)
	{
		u32 chan_num = (addr >> 4) & 0xF;
		channel_struct &thischan=channels[chan_num];

		//printf("SPU write32 channel%d, reg %04X, val %08X\n", chan_num, addr, val);

		switch (addr & 0xF)
		{
			case 0x0: 
				thischan.vol = val & 0x7F;
				thischan.volumeDiv = (val >> 8) & 0x3;
				thischan.hold = (val >> 15) & 0x1;
				thischan.pan = (val >> 16) & 0x7F;
				thischan.waveduty = (val >> 24) & 0x7;
				thischan.repeat = (val >> 27) & 0x3;
				thischan.format = (val >> 29) & 0x3;
				thischan.keyon = (val >> 31) & 0x1;
				KeyProbe(chan_num);
			break;

			case 0x4: thischan.addr = (val & 0x07FFFFFC); break;
			case 0x8: 
				thischan.timer = (val & 0xFFFF);
				thischan.loopstart = ((val >> 16) & 0xFFFF);
				adjust_channel_timer(&thischan);
			break;

			case 0xC: thischan.length = (val & 0x003FFFFF); break; //only 22 bits of this register are used
		} //switch on individual channel regs
		return;
	}

	switch(addr)
	{
		//SOUNDCNT
		case 0x500:
			regs.mastervol = (val & 0x7F);
			regs.ctl_left  = ((val >> 8) & 3);
			regs.ctl_right = ((val>>10) & 3);
			regs.ctl_ch1bypass = ((val >> 12) & 1);
			regs.ctl_ch3bypass = ((val >> 13) & 1);
			regs.masteren = ((val >> 15) & 1);
			for(u8 i=0; i<16; i++)
				KeyProbe(i);
			break;

		//SOUNDBIAS
		case 0x504: regs.soundbias = (val & 0x3FF);

		//SNDCAP0CNT/SNDCAP1CNT
		case 0x508:
			regs.cap[0].add = BIT0(val);
			regs.cap[0].source = BIT1(val);
			regs.cap[0].oneshot = BIT2(val);
			regs.cap[0].bits8 = BIT3(val);
			regs.cap[0].active = BIT7(val);
			ProbeCapture(0);

			regs.cap[1].add = BIT8(val);
			regs.cap[1].source = BIT9(val);
			regs.cap[1].oneshot = BIT10(val);
			regs.cap[1].bits8 = BIT11(val);
			regs.cap[1].active = BIT15(val);
			ProbeCapture(1);
		break;

		//SNDCAP0DAD
		case 0x510: regs.cap[0].dad = (val & 0x07FFFFFC); break;

		//SNDCAP0LEN
		case 0x514: regs.cap[0].len = (val & 0xFFFF); break;

		//SNDCAP1DAD
		case 0x518: regs.cap[1].dad = (val & 0x07FFFFFC); break;

		//SNDCAP1LEN
		case 0x51C: regs.cap[1].len = (val & 0xFFFF); break;
	} //switch on address
}

static FORCEINLINE void FetchADPCMData(channel_struct * const chan, s32 * const data)
{
   const u32 endExclusive = sputrunc(chan->sampcnt+1);
   for (u32 i = chan->lastsampcnt+1; i < endExclusive; i++)
   {
      const u32 shift = (i&1)<<2;
      const u32 data4bit = ((u32)read08(chan->addr + (i>>1))) >> shift;

      const s32 diff = precalcdifftbl[chan->index][data4bit & 0xF];
      chan->index = precalcindextbl[chan->index][data4bit & 0x7];

      chan->pcm16b_last = chan->pcm16b;
      chan->pcm16b = MinMax(chan->pcm16b+diff, -0x8000, 0x7FFF);

      if(i == (chan->loopstart<<3)) {
         if(chan->loop_index != K_ADPCM_LOOPING_RECOVERY_INDEX) printf("over-snagging\n");
         chan->loop_pcm16b = chan->pcm16b;
         chan->loop_index = chan->index;
      }
   }

   chan->lastsampcnt = sputrunc(chan->sampcnt);

   *data = (s32)chan->pcm16b;
}

static FORCEINLINE void FetchPSGData(channel_struct *chan, s32 *data)
{
   if(chan->lastsampcnt == sputrunc(chan->sampcnt))
   {
      *data = (s32)chan->psgnoise_last;
      return;
   }

   u32 max = sputrunc(chan->sampcnt);
   for(u32 i = chan->lastsampcnt; i < max; i++)
   {
      if(chan->x & 0x1)
      {
         chan->x = (chan->x >> 1) ^ 0x6000;
         chan->psgnoise_last = -0x7FFF;
      }
      else
      {
         chan->x >>= 1;
         chan->psgnoise_last = 0x7FFF;
      }
   }

   chan->lastsampcnt = sputrunc(chan->sampcnt);

   *data = (s32)chan->psgnoise_last;
}

//////////////////////////////////////////////////////////////////////////////

static FORCEINLINE int TestForLoop(SPU_struct *SPU, channel_struct *chan, int shift)
{
	if (chan->sampcnt > chan->double_totlength_shifted)
   {
      // Do we loop? Or are we done?
      if (chan->repeat == 1)
      {
         while (chan->sampcnt > chan->double_totlength_shifted)
            chan->sampcnt -= chan->double_totlength_shifted - (double)(chan->loopstart << shift);
         return 0;
      }

      SPU->KeyOff(chan->num);
      SPU->bufpos = SPU->buflength;
      return 1;
   }

   return -1;
}

//WORK
template<int FORMAT, int CHANNELS> 
	FORCEINLINE static void ____SPU_ChanUpdate(SPU_struct* const SPU, channel_struct* const chan)
{
	for (; SPU->bufpos < SPU->buflength; SPU->bufpos++)
	{
      int ret;
      int shift = (FORMAT == 0 ? 2 : 1);

		if(CHANNELS != -1)
		{
			s32 data;

         if (chan->sampcnt < 0)
            data = 0;
         else
         {
            switch(FORMAT)
            {
               case 0: /* Fetch 8-bit data */
                  data = (s32)read_s8(chan->addr + sputrunc(chan->sampcnt))<< 8;
                  break;
               case 1: /* Fetch 16-bit data */
                  data = read16(chan->addr + sputrunc(chan->sampcnt)*2);
                  break;
               case 2: /* Fetch ADPCM data */
                  /* No sense decoding, just return the last sample */
                  if (chan->lastsampcnt == sputrunc(chan->sampcnt))
                     data = (s32)chan->pcm16b;
                  else
                     FetchADPCMData(chan, &data);
                  break;
               case 3: /* Fetch PSG data */
                  if(chan->num < 8)
                     data = 0;
                  else if(chan->num < 14)
                     data = (s32)wavedutytbl[chan->waveduty][(sputrunc(chan->sampcnt)) & 0x7];
                  else
                     FetchPSGData(chan, &data);
                  break;
            }
         }

         data = spumuldiv7(data, chan->vol) >> volume_shift[chan->volumeDiv];

         switch(CHANNELS)
         {
            case 0: /* MixL */
               SPU->sndbuf[SPU->bufpos<<1]     += data;
               break;
            case 1: /* MixLR */
               SPU->sndbuf[SPU->bufpos<<1]     += spumuldiv7(data, 127 - chan->pan);
               SPU->sndbuf[(SPU->bufpos<<1)+1] += spumuldiv7(data, chan->pan);
               break;
            case 2: /* MixR */
               SPU->sndbuf[(SPU->bufpos<<1)+1] += data;
               break;
            case -1:
               break;
         }

         SPU->lastdata = data;
		}

      // Minimum length (the sum of PNT+LEN) is 4 words (16 bytes), 
      // smaller values (0..3 words) are causing hang-ups 
      // (busy bit remains set infinite, but no sound output occurs).
      // fix: 7th Dragon (JP) - http://sourceforge.net/p/desmume/bugs/1357/
      if (FORMAT == 2 && chan->totlength < 4)
         continue;

      chan->sampcnt += chan->sampinc;

		switch(FORMAT)
      {
         case 2:
            shift = 3;
            /* fall-through */
         case 0:
         case 1:
            ret   = TestForLoop(SPU, chan, shift);

            if (FORMAT == 2 && ret != -1)
            {
               switch (ret)
               {
                  case 0:
                     if(chan->loop_index == K_ADPCM_LOOPING_RECOVERY_INDEX)
                     {
                        chan->pcm16b = (s16)read16(chan->addr);
                        chan->index = read08(chan->addr+2) & 0x7F;
                        chan->lastsampcnt = 7;
                     }
                     else
                     {
                        chan->pcm16b = chan->loop_pcm16b;
                        chan->index = chan->loop_index;
                        chan->lastsampcnt = (chan->loopstart << 3);
                     }
                     break;
                  case 1:
                     chan->status = CHANSTAT_STOPPED;
                     break;
               }
            }
            break;
         case 3:
            break;
      }
	}
}

template<int FORMAT> 
	FORCEINLINE static void ___SPU_ChanUpdate(const bool actuallyMix, SPU_struct* const SPU, channel_struct* const chan)
{
	if(!actuallyMix)
		____SPU_ChanUpdate<FORMAT,-1>(SPU,chan);
	else if (chan->pan == 0)
		____SPU_ChanUpdate<FORMAT,0>(SPU,chan);
	else if (chan->pan == 127)
		____SPU_ChanUpdate<FORMAT,2>(SPU,chan);
	else
		____SPU_ChanUpdate<FORMAT,1>(SPU,chan);
}

//ENTERNEW
static void SPU_MixAudio_Advanced(SPU_struct *SPU, int length)
{
	//the advanced spu function correctly handles all sound control mixing options, as well as capture
	//this code is not entirely optimal, as it relies on sort of manhandling the core mixing functions
	//in order to get the results it needs.

	//THIS IS MAX HACKS!!!!
	//AND NEEDS TO BE REWRITTEN ALONG WITH THE DEEPEST PARTS OF THE SPU
	//ONCE WE KNOW THAT IT WORKS
	
	//BIAS gets ignored since our spu is still not bit perfect,
	//and it doesnt matter for purposes of capture

	s32 samp0[2];
	
	//believe it or not, we are going to do this one sample at a time.
	//like i said, it is slower.
	for(int samp=0;samp<length;samp++)
	{
		SPU->sndbuf[0] = 0;
		SPU->sndbuf[1] = 0;
		SPU->buflength = 1;

		s32 capmix[2] = {0,0};
		s32 mix[2] = {0,0};
		s32 chanout[16];
		s32 submix[32];

		//generate each channel, and helpfully mix it at the same time
		for(int i=0;i<16;i++)
		{
			channel_struct *chan = &SPU->channels[i];

			if (chan->status == CHANSTAT_PLAY)
			{
				SPU->bufpos = 0;

				bool bypass = false;
				if(i==1 && SPU->regs.ctl_ch1bypass) bypass=true;
				if(i==3 && SPU->regs.ctl_ch3bypass) bypass=true;


				//output to mixer unless we are bypassed.
				//dont output to mixer if the user muted us
				bool outputToMix = true;
				if(bypass) outputToMix = false;
				bool outputToCap = outputToMix;

				//channels 1 and 3 should probably always generate their audio
				//internally at least, just in case they get used by the spu output
				bool domix = outputToCap || outputToMix || i==1 || i==3;

				//clear the output buffer since this is where _SPU_ChanUpdate wants to accumulate things
				SPU->sndbuf[0] = SPU->sndbuf[1] = 0;

				//get channel's next output sample.
            switch(chan->format)
            {
               case 0:
                  ___SPU_ChanUpdate<0>(domix, SPU, chan);
                  break;
               case 1:
                  ___SPU_ChanUpdate<1>(domix, SPU, chan);
                  break;
               case 2:
                  ___SPU_ChanUpdate<2>(domix, SPU, chan);
                  break;
               case 3:
                  ___SPU_ChanUpdate<3>(domix, SPU, chan);
                  break;
            }
				chanout[i] = SPU->lastdata >> volume_shift[chan->volumeDiv];

				//save the panned results
				submix[i*2] = SPU->sndbuf[0];
				submix[i*2+1] = SPU->sndbuf[1];

				//send sample to our capture mix
				if(outputToCap)
				{
					capmix[0] += submix[i*2];
					capmix[1] += submix[i*2+1];
				}

				//send sample to our main mixer
				if(outputToMix)
				{
					mix[0] += submix[i*2];
					mix[1] += submix[i*2+1];
				}
			}
			else 
			{
				chanout[i] = 0;
				submix[i*2] = 0;
				submix[i*2+1] = 0;
			}
		} //foreach channel

		s32 mixout[2] = {mix[0],mix[1]};
		s32 capmixout[2] = {capmix[0],capmix[1]};
		s32 sndout[2];
		s32 capout[2];

		//create SPU output
		switch(SPU->regs.ctl_left)
		{
		case SPU_struct::REGS::LOM_LEFT_MIXER: sndout[0] = mixout[0]; break;
		case SPU_struct::REGS::LOM_CH1: sndout[0] = submix[1*2+0]; break;
		case SPU_struct::REGS::LOM_CH3: sndout[0] = submix[3*2+0]; break;
		case SPU_struct::REGS::LOM_CH1_PLUS_CH3: sndout[0] = submix[1*2+0] + submix[3*2+0]; break;
		}
		switch(SPU->regs.ctl_right)
		{
		case SPU_struct::REGS::ROM_RIGHT_MIXER: sndout[1] = mixout[1]; break;
		case SPU_struct::REGS::ROM_CH1: sndout[1] = submix[1*2+1]; break;
		case SPU_struct::REGS::ROM_CH3: sndout[1] = submix[3*2+1]; break;
		case SPU_struct::REGS::ROM_CH1_PLUS_CH3: sndout[1] = submix[1*2+1] + submix[3*2+1]; break;
		}


		//generate capture output ("capture bugs" from gbatek are not emulated)
		if(SPU->regs.cap[0].source==0) 
			capout[0] = capmixout[0]; //cap0 = L-mix
		else if(SPU->regs.cap[0].add)
			capout[0] = chanout[0] + chanout[1]; //cap0 = ch0+ch1
		else capout[0] = chanout[0]; //cap0 = ch0

		if(SPU->regs.cap[1].source==0) 
			capout[1] = capmixout[1]; //cap1 = R-mix
		else if(SPU->regs.cap[1].add)
			capout[1] = chanout[2] + chanout[3]; //cap1 = ch2+ch3
		else capout[1] = chanout[2]; //cap1 = ch2

		capout[0] = MinMax(capout[0],-0x8000,0x7FFF);
		capout[1] = MinMax(capout[1],-0x8000,0x7FFF);

		//write the output sample where it is supposed to go
		if(samp==0)
		{
			samp0[0] = sndout[0];
			samp0[1] = sndout[1];
		}
		else
		{
			SPU->sndbuf[samp*2+0] = sndout[0];
			SPU->sndbuf[samp*2+1] = sndout[1];
		}

		for(int capchan=0;capchan<2;capchan++)
		{
			if(SPU->regs.cap[capchan].runtime.running)
			{
				SPU_struct::REGS::CAP& cap = SPU->regs.cap[capchan];
				u32 last = sputrunc(cap.runtime.sampcnt);
				cap.runtime.sampcnt += SPU->channels[1+2*capchan].sampinc;
				u32 curr = sputrunc(cap.runtime.sampcnt);
				for(u32 j=last;j<curr;j++)
				{
					//so, this is a little strange. why go through a fifo?
					//it seems that some games will set up a reverb effect by capturing
					//to the nearly same address as playback, but ahead by a couple.
					//So, playback will always end up being what was captured a couple of samples ago.
					//This system counts on playback always having read ahead 16 samples.
					//In that case, playback will end up being what was processed at one entire buffer length ago,
					//since the 16 samples would have read ahead before they got captured over

					//It's actually the source channels which should have a fifo, but we are
					//not going to take the hit in speed and complexity. Save it for a future rewrite.
					//Instead, what we do here is delay the capture by 16 samples to create a similar effect.
					//Subjectively, it seems to be working.

					//Don't do anything until the fifo is filled, so as to delay it
					if(cap.runtime.fifo.size<16)
					{
						cap.runtime.fifo.enqueue(capout[capchan]);
						continue;
					}

					//(actually capture sample from fifo instead of most recently generated)
					u32 multiplier;
					s32 sample = cap.runtime.fifo.dequeue();
					cap.runtime.fifo.enqueue(capout[capchan]);

					if(cap.bits8)
					{
						s8 sample8 = sample>>8;
                  _MMU_write08<1,MMU_AT_DMA>(cap.runtime.curdad,sample8);
						cap.runtime.curdad++;
						multiplier = 4;
					}
					else
					{
						s16 sample16 = sample;
                  _MMU_write16<1,MMU_AT_DMA>(cap.runtime.curdad,sample16);
						cap.runtime.curdad+=2;
						multiplier = 2;
					}

					if(cap.runtime.curdad>=cap.runtime.maxdad) {
						cap.runtime.curdad = cap.dad;
						cap.runtime.sampcnt -= cap.len*multiplier;
					}
				} //sampinc loop
			} //if capchan running
		} //capchan loop
	} //main sample loop

	SPU->sndbuf[0] = samp0[0];
	SPU->sndbuf[1] = samp0[1];
}

//ENTER
static void SPU_MixAudio(SPU_struct *SPU, int length)
{
   u8 vol;
   bool advanced;

   memset(SPU->sndbuf, 0, length*4*2);

	//we used to use master enable here, and do nothing if audio is disabled.
	//now, master enable is emulated better..
	//but for a speed optimization we will still do it
	if(!SPU->regs.masteren) return;

	vol        = SPU->regs.mastervol;
	advanced   = CommonSettings.spu_advanced ;

	//branch here so that slow computers don't have to take the advanced (slower) codepath.
	//it remains to be seen exactly how much slower it is
	//if it isnt much slower then we should refactor everything to be simpler, once it is working
	if(advanced && SPU == SPU_core)
		SPU_MixAudio_Advanced(SPU, length);
	else
	{
		//non-advanced mode
		for(int i=0;i<16;i++)
		{
			channel_struct *chan = &SPU->channels[i];

			if (chan->status != CHANSTAT_PLAY)
				continue;

			SPU->bufpos = 0;
			SPU->buflength = length;

			// Mix audio
         switch(chan->format)
         {
            case 0:
               ___SPU_ChanUpdate<0>(true, SPU, chan);
               break;
            case 1:
               ___SPU_ChanUpdate<1>(true, SPU, chan);
               break;
            case 2:
               ___SPU_ChanUpdate<2>(true, SPU, chan);
               break;
            case 3:
               ___SPU_ChanUpdate<3>(true, SPU, chan);
               break;
         }
		}
	}


	// convert from 32-bit->16-bit
   for (int i = 0; i < length*2; i++)
   {
      // Apply Master Volume
      SPU->sndbuf[i] = SPU->sndbuf[i] * vol / 127;
      s16 outsample = MinMax(SPU->sndbuf[i],-0x8000,0x7FFF);
      SPU->outbuf[SPU->outbufptr++] = outsample;
   }
}

//////////////////////////////////////////////////////////////////////////////


//emulates one hline of the cpu core.
//this will produce a variable number of samples, calculated to keep a 44100hz output
//in sync with the emulator framerate
int spu_core_samples = 0;

void SPU_Emulate_core()
{
	samples += samples_per_hline;
	spu_core_samples = (int)(samples);
	samples -= spu_core_samples;
	
	SPU_MixAudio(SPU_core, spu_core_samples);
}

void SPU_Emulate_user(bool mix)
{
   // FIXME: sometimes outbufptr > 735*2, figure out what to do with the extra samples
   audio_batch_cb(SPU_core->outbuf, 735);
   memset(SPU_core->outbuf, 0, 735 * 2 * sizeof(s16));
   SPU_core->outbufptr = 0;
}

void spu_savestate(EMUFILE* os)
{
	//version
	write32le(6,os);

	SPU_struct *spu = SPU_core;

	for(int j=0;j<16;j++) {
		channel_struct &chan = spu->channels[j];
		write32le(chan.num,os);
		write8le(chan.vol,os);
		write8le(chan.volumeDiv,os);
		write8le(chan.hold,os);
		write8le(chan.pan,os);
		write8le(chan.waveduty,os);
		write8le(chan.repeat,os);
		write8le(chan.format,os);
		write8le(chan.status,os);
		write32le(chan.addr,os);
		write16le(chan.timer,os);
		write16le(chan.loopstart,os);
		write32le(chan.length,os);
		write64le(double_to_u64(chan.sampcnt),os);
		write64le(double_to_u64(chan.sampinc),os);
		write32le(chan.lastsampcnt,os);
		write16le(chan.pcm16b,os);
		write16le(chan.pcm16b_last,os);
		write32le(chan.index,os);
		write16le(chan.x,os);
		write16le(chan.psgnoise_last,os);
		write8le(chan.keyon,os);
	}

	write64le(double_to_u64(samples),os);

	write8le(spu->regs.mastervol,os);
	write8le(spu->regs.ctl_left,os);
	write8le(spu->regs.ctl_right,os);
	write8le(spu->regs.ctl_ch1bypass,os);
	write8le(spu->regs.ctl_ch3bypass,os);
	write8le(spu->regs.masteren,os);
	write16le(spu->regs.soundbias,os);

	for(int i=0;i<2;i++)
	{
		write8le(spu->regs.cap[i].add,os);
		write8le(spu->regs.cap[i].source,os);
		write8le(spu->regs.cap[i].oneshot,os);
		write8le(spu->regs.cap[i].bits8,os);
		write8le(spu->regs.cap[i].active,os);
		write32le(spu->regs.cap[i].dad,os);
		write16le(spu->regs.cap[i].len,os);
		write8le(spu->regs.cap[i].runtime.running,os);
		write32le(spu->regs.cap[i].runtime.curdad,os);
		write32le(spu->regs.cap[i].runtime.maxdad,os);
		write_double_le(spu->regs.cap[i].runtime.sampcnt,os);
	}

	for(int i=0;i<2;i++)
		spu->regs.cap[i].runtime.fifo.save(os);
}

bool spu_loadstate(EMUFILE* is, int size)
{
	u64 temp64; 
	
	//read version
	u32 version;
	if(read32le(&version,is) != 1) return false;

	SPU_struct *spu = SPU_core;
	reconstruct(&SPU_core->regs);

	for(int j=0;j<16;j++) {
		channel_struct &chan = spu->channels[j];
		read32le(&chan.num,is);
		read8le(&chan.vol,is);
		read8le(&chan.volumeDiv,is);
		if (chan.volumeDiv == 4) chan.volumeDiv = 3;
		read8le(&chan.hold,is);
		read8le(&chan.pan,is);
		read8le(&chan.waveduty,is);
		read8le(&chan.repeat,is);
		read8le(&chan.format,is);
		read8le(&chan.status,is);
		read32le(&chan.addr,is);
		read16le(&chan.timer,is);
		read16le(&chan.loopstart,is);
		read32le(&chan.length,is);
		chan.totlength = chan.length + chan.loopstart;
		chan.double_totlength_shifted = (double)(chan.totlength << format_shift[chan.format]);
		//printf("%f\n",chan.double_totlength_shifted);
		if(version >= 2)
		{
			read64le(&temp64,is); chan.sampcnt = u64_to_double(temp64);
			read64le(&temp64,is); chan.sampinc = u64_to_double(temp64);
		}
		else
		{
			read32le((u32*)&chan.sampcnt,is);
			read32le((u32*)&chan.sampinc,is);
		}
		read32le(&chan.lastsampcnt,is);
		read16le(&chan.pcm16b,is);
		read16le(&chan.pcm16b_last,is);
		read32le(&chan.index,is);
		read16le(&chan.x,is);
		read16le(&chan.psgnoise_last,is);

		if(version>=4)
			read8le(&chan.keyon,is);

		//hopefully trigger a recovery of the adpcm looping system
		chan.loop_index = K_ADPCM_LOOPING_RECOVERY_INDEX;
	}

	if(version>=2) {
		read64le(&temp64,is); samples = u64_to_double(temp64);
	}

	if(version>=4)
	{
		read8le(&spu->regs.mastervol,is);
		read8le(&spu->regs.ctl_left,is);
		read8le(&spu->regs.ctl_right,is);
		read8le(&spu->regs.ctl_ch1bypass,is);
		read8le(&spu->regs.ctl_ch3bypass,is);
		read8le(&spu->regs.masteren,is);
		read16le(&spu->regs.soundbias,is);
	}

	if(version>=5)
	{
		for(int i=0;i<2;i++)
		{
			read8le(&spu->regs.cap[i].add,is);
			read8le(&spu->regs.cap[i].source,is);
			read8le(&spu->regs.cap[i].oneshot,is);
			read8le(&spu->regs.cap[i].bits8,is);
			read8le(&spu->regs.cap[i].active,is);
			read32le(&spu->regs.cap[i].dad,is);
			read16le(&spu->regs.cap[i].len,is);
			read8le(&spu->regs.cap[i].runtime.running,is);
			read32le(&spu->regs.cap[i].runtime.curdad,is);
			read32le(&spu->regs.cap[i].runtime.maxdad,is);
			read_double_le(&spu->regs.cap[i].runtime.sampcnt,is);
		}
	}

	if(version>=6)
		for(int i=0;i<2;i++) spu->regs.cap[i].runtime.fifo.load(is);
	else
		for(int i=0;i<2;i++) spu->regs.cap[i].runtime.fifo.reset();

	//older versions didnt store a mastervol; 
	//we must reload this or else games will start silent
	if(version<4)
	{
		spu->regs.mastervol = T1ReadByte(MMU.ARM7_REG, 0x500) & 0x7F;
		spu->regs.masteren = BIT15(T1ReadWord(MMU.ARM7_REG, 0x500));
	}

	//copy the core spu (the more accurate) to the user spu
	SPU_CloneUser();

	return true;
}
